// Copyright (C) 2025, ZWSOFT Co., LTD.(Guangzhou)
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json.Serialization;
using System.Windows.Media;
using NativeLibrary = TestClient.Domain.Util.NativeLibrary;

namespace TestClient.Domain;

#region Native data structure

enum VFResult
{
    Success = 0, Failure = 1, Cancelled = 2
}

public enum GeomEntityType
{
    None = -1,
    Any = 0,
    Point, Curve, Surface, Body
}

enum VfErrorSource
{
    Unknown = 0,
    CadErr,
    Rpc,
}

[StructLayout(LayoutKind.Sequential)]
struct VfLastErrorNative
{
    public VfErrorSource source;
    public int err_code;
    public IntPtr err_what;
}

#endregion

#region Exceptions

[Serializable]
public class VfApiException : Exception
{
    public int ErrCode { get; init; }

    public VfApiException()
    { }

    public VfApiException(string message)
        : base(message)
    { }

    public VfApiException(string message, Exception innerException)
        : base(message, innerException)
    { }
}

#endregion

public struct Vector3d
{
    public double x;
    public double y;
    public double z;

    public Vector3d(double x, double y, double z) => (this.x, this.y, this.z) = (x, y, z);
    public Vector3d() : this(0, 0, 0) { }

    public override readonly string ToString() => $"({x:0.#####}, {y:0.#####}, {z:0.#####})";
    public static readonly Vector3d Zero = new();

    public static Vector3d operator +(Vector3d v1, Vector3d v2)
    {
        return new Vector3d(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
    }
    public static Vector3d operator -(Vector3d v1, Vector3d v2)
    {
        return new Vector3d(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
    }
    public static Vector3d operator *(Vector3d v, double s)
    {
        return new Vector3d(v.x * s, v.y * s, v.z * s);
    }
    public static Vector3d operator /(Vector3d v, double s)
    {
        return new Vector3d(v.x / s, v.y / s, v.z / s);
    }
    public static Vector3d operator *(double s, Vector3d v)
    {
        return new Vector3d(s * v.x, s * v.y, s * v.z);
    }

    public readonly Vector3d Normalized()
    {
        return this / Magnitude();
    }

    public readonly double Dot(Vector3d v)
    {
        return x * v.x + y * v.y + z * v.z;
    }

    public readonly Vector3d Cross(Vector3d v)
    {
        double rx = y * v.z - z * v.y;
        double ry = z * v.x - x * v.z;
        double rz = x * v.y - y * v.x;
        return new Vector3d(rx, ry, rz);
    }

    public readonly double Magnitude()
    {
        return Math.Sqrt(x * x + y * y + z * z);
    }

    public readonly double SquaredMagnitude()
    {
        return x * x + y * y + z * z;
    }

    public static bool TryParse(string s, out Vector3d v)
    {
        s = s.Trim('(', ')', ' ');
        string[] parts = s.Split([',', '|', ';'], StringSplitOptions.RemoveEmptyEntries);

        if (parts.Length == 3 &&
            double.TryParse(parts[0], out double x) &&
            double.TryParse(parts[1], out double y) &&
            double.TryParse(parts[2], out double z))
        {
            v = new Vector3d(x, y, z);
            return true;
        }
        v = new Vector3d();
        return false;
    }
}

public struct Vector2d
{
    public double x;
    public double y;

    public Vector2d(double x, double y) => (this.x, this.y) = (x, y);
    public Vector2d() => (x, y) = (0, 0);

    public override readonly string ToString() => $"({x:0.#####}, {y:0.#####})";
}

public class MBDTreeNode
{
    [JsonPropertyName("valueType")]
    public int ValueType { get; set; }

    [JsonPropertyName("nodeName")]
    public required string NodeName { get; set; }

    [JsonPropertyName("key")]
    public required string Key { get; set; }

    [JsonPropertyName("value")]
    public required string Value { get; set; }

    [JsonPropertyName("childNodes")]
    public List<MBDTreeNode> ChildNodes { get; set; } = [];
}

public class VFAdapterWrapper
{
    private NativeLibrary? library;

    private NativeLibrary Library => library ?? throw new InvalidOperationException("DLL is not loaded yet");
    public bool IsLoaded => library != null;

    public VFAdapterWrapper()
    {
    }

    public void LoadLibrary(string path)
    {
        library?.Dispose();
        library = new NativeLibrary(path);
    }

    public void UnloadLibrary()
    {
        library?.Dispose();
        library = null;
    }

    #region Delegates
    private delegate VFResult vfStartLinkWithCAD(int type);
    private delegate VFResult vfSelectionToID(
        [MarshalAs(UnmanagedType.LPWStr)] string input,
        [MarshalAs(UnmanagedType.LPStr)] StringBuilder output);
    private delegate VFResult vfChangeLineColorWidthInsertMap(IntPtr[] input);
    private delegate VFResult vfChangeLineColorWidthFromMap(IntPtr[] input);
    private delegate VFResult vfCurveCurveIntersection(
        [MarshalAs(UnmanagedType.LPStr)] string curveId1,
        [MarshalAs(UnmanagedType.LPStr)] string curveId2,
        out double x,
        out double y,
        out double z,
        out IntPtr pointIdStr);
    private delegate VFResult vfCurveSurfaceIntersection(
        [MarshalAs(UnmanagedType.LPStr)] string curveId,
        [MarshalAs(UnmanagedType.LPStr)] string surfaceId,
        out double x,
        out double y,
        out double z,
        out IntPtr pointIdStr);
    private delegate VFResult vfSurfaceArea([MarshalAs(UnmanagedType.LPStr)] string surfaceID, out double dArea);
    private delegate VFResult vfSurfacePerimeter([MarshalAs(UnmanagedType.LPStr)] string surfaceID, out double dPerimeter);
    private delegate VFResult vfSurfaceWeight(
        [MarshalAs(UnmanagedType.LPStr)] string surfaceID,
        double SurfaceDensity,
        out double dWeight);
    private delegate VFResult vfCreateGravityOfSurface(
        [MarshalAs(UnmanagedType.LPStr)] string surfaceID,
        out double x,
        out double y,
        out double z);
    private delegate VFResult vfTestPointOnCurve(
        [MarshalAs(UnmanagedType.LPStr)] string pointID,
        [MarshalAs(UnmanagedType.LPStr)] string curveID,
        out bool bOn);
    private delegate VFResult vfTestPointOnSurface(
        [MarshalAs(UnmanagedType.LPStr)] string pointID,
        [MarshalAs(UnmanagedType.LPStr)] string surfaceId,
        out int result);
    private delegate VFResult vfTestPointOnSurface2(
        double x,
        double y,
        double z,
        [MarshalAs(UnmanagedType.LPStr)] string surfaceId,
        out int result);
    private delegate VFResult vfCreateCurveBySurfaceIntersection(
        [MarshalAs(UnmanagedType.LPStr)] string surfaceID1,
        [MarshalAs(UnmanagedType.LPStr)] string surfaceID2,
        out IntPtr curveID);
    private delegate VFResult vfCurveLength([MarshalAs(UnmanagedType.LPStr)] string CurveID, out double Length);
    private delegate VFResult vfGetUnitTangentVectorAtPoint(
        [MarshalAs(UnmanagedType.LPStr)] string CurveID,
        [MarshalAs(UnmanagedType.LPStr)] string PointID,
        int bOn,
        out double x,
        out double y,
        out double z);
    private delegate VFResult vfGetUnitNormalVectorAtPoint(
        [MarshalAs(UnmanagedType.LPStr)] string CurveID,
        [MarshalAs(UnmanagedType.LPStr)] string PointID,
        int bOn,
        out double x,
        out double y,
        out double z);
    private delegate VFResult vfCreateNormalLineAndArrowhead(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        [MarshalAs(UnmanagedType.LPStr)] string PointID);
    private delegate VFResult vfDeleteNormalLineAndArrowhead([MarshalAs(UnmanagedType.LPStr)] string PointID);
    private delegate VFResult vfTestPointOnSurfaceByProject(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        double px,
        double py,
        double pz,
        double dx,
        double dy,
        double dz,
        out int result);
    private delegate VFResult vfGetCoordOfPoint(
        [MarshalAs(UnmanagedType.LPStr)] string PointID,
        out double x,
        out double y,
        out double z);
    private delegate VFResult vfCreateSplitOrTrim(
        [MarshalAs(UnmanagedType.LPStr)] string CurveID1,
        [MarshalAs(UnmanagedType.LPStr)] string CurveID2,
        [MarshalAs(UnmanagedType.LPStr)] string CutType,
        int iOrientation1,
        int iOrientation2);
    private delegate VFResult vfOffsetSurface(
        [MarshalAs(UnmanagedType.LPStr)] string surfaceID1,
        [MarshalAs(UnmanagedType.LPStr)] string pointID,
        double dis,
        out IntPtr surfaceID2);
    // Note: IntersectCurveID is const char**&
    private delegate VFResult vfGetIntersectCurves([MarshalAs(UnmanagedType.LPStr)] string CurveID, out IntPtr IntersectCurveID);
    private delegate VFResult vfSelectionToIDMore([MarshalAs(UnmanagedType.LPWStr)] string filterType, out IntPtr outputID);

    private delegate VFResult vfAddCommentOnPoint(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        double x, double y, double z,
        [MarshalAs(UnmanagedType.BStr)] string Comment,
        int R, int G, int B,
        [MarshalAs(UnmanagedType.BStr)] string Font,
        double size,
        out IntPtr outputID);

    private delegate VFResult vfDeleteCommentOnPoint([MarshalAs(UnmanagedType.LPStr)] string PointID);
    private delegate VFResult vfChangePointColorInsertMap(IntPtr[] inputStringValue, int colorR, int colorG, int colorB);
    private delegate VFResult vfChangePointColorFromMap(IntPtr[] inputStringValue);
    private delegate VFResult vfChangeSurfaceColorInsertMap(IntPtr[] surfaces, int[] R, int[] G, int[] B);
    private delegate VFResult vfChangeSurfaceColorFromMap(IntPtr[] surfaces);

    private delegate VFResult vfCreateCurveOnSurfaceByTwoPoints(
        double[] X1,
        double[] Y1,
        double[] Z1,
        double[] X2,
        double[] Y2,
        double[] Z2,
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID);
    private delegate VFResult vfGeodesicLengthOfPointToCurve(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        [MarshalAs(UnmanagedType.LPStr)] string CurveID,
        [MarshalAs(UnmanagedType.LPStr)] string PointID,
        double x,
        double y,
        double z,
        out double GeodesicLength);
    // pick this out of 3 because it is the most verbose one
    private delegate VFResult vfGetPointByGeodesicDis(
        [MarshalAs(UnmanagedType.LPStr)] string surfaceID,
        double x,
        double y,
        double z,
        double dirx,
        double diry,
        double dirz,
        double geodesicDis,
        out double outputx,
        out double outputy,
        out double outputz,
        out int result);
    private delegate VFResult vfCreatePointByProjection(
        [MarshalAs(UnmanagedType.LPStr)] string PointID1,
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        out IntPtr PointID2);
    private delegate VFResult vfGetActiveDocumentName([MarshalAs(UnmanagedType.BStr)] out string ActiveDocumentName);
    private delegate VFResult vfGetCurveStartAndEndPointCoordinates([MarshalAs(UnmanagedType.LPStr)] string CurveID,
        out double Startx, out double Starty, out double Startz, out double Endx, out double Endy, out double Endz);
    private delegate VFResult vfGetEquidistantPointsOnCurve(
        [MarshalAs(UnmanagedType.LPStr)] string CurveID,
        int PointNums,
        // double * for all intptr
        out IntPtr X,
        out IntPtr Y,
        out IntPtr Z);
    private delegate VFResult vfGetEquidistantPointsOnCurveRing(
        [MarshalAs(UnmanagedType.LPStr)] string CurveID,
        int PointNums,
        // double * for all intptr
        out IntPtr X,
        out IntPtr Y,
        out IntPtr Z);
    private delegate VFResult vfDismantleCurve(
        [MarshalAs(UnmanagedType.LPStr)] string CurveID,
        out IntPtr DismantlesID,
        out IntPtr LineOrCurve,
        out int length);
    private delegate VFResult vfGetMinimumDistancePointCoordinates(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        double X1,
        double Y1,
        double Z1,
        out double X2,
        out double Y2,
        out double Z2);
    private delegate VFResult vfCreateSpline2D([MarshalAs(UnmanagedType.BStr)] string Name,
        int Plane,
        int length,
        double[] PointCoordinate1,
        double[] PointCoordinate2);
    private delegate VFResult vfDeleteCurves(IntPtr[] curves);
    private delegate VFResult vfDeleteSurfaces(IntPtr[] surfaces);
    private delegate VFResult vfTrimOrExtendCurvesFromEndPoint(
        IntPtr[] Curves,
        [MarshalAs(UnmanagedType.LPStr)] string Surface,
        double[] X,
        double[] Y,
        double[] Z,
        double[] Offsetength);
    // Note: OffsetCurves is const char**&
    private delegate VFResult vfOffsetCurvesByParallel(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        [MarshalAs(UnmanagedType.LPStr)] string CurveID,
        double x, double y, double z, int DistanceNums,
        double[] GeodesicDistances,
        [MarshalAs(UnmanagedType.BStr)] string GeometricSetName,
        IntPtr GeodesicCurvesName,
        out IntPtr OffsetCurves);
    private delegate VFResult vfOffsetCurvesByParallel2(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        [MarshalAs(UnmanagedType.LPStr)] string CurveID,
        double x, double y, double z,
        int DistanceNums,
        double[] GeodesicDistances,
        [MarshalAs(UnmanagedType.BStr)] string GeometricSetName,
        IntPtr GeodesicCurvesName,
        out IntPtr OffsetCurves);
    private delegate VFResult vfOffsetCurvesBySymmetry(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        [MarshalAs(UnmanagedType.LPStr)] string CurveID,
        double[] GeodesicDistances,
        out IntPtr OffsetCurves);
    private delegate VFResult vfOffsetCurveByStartPointAndEndPoint(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        [MarshalAs(UnmanagedType.LPStr)] string CurveID,
        [MarshalAs(UnmanagedType.LPStr)] string Orientation,
        double[] StartPointGeodesicDistances,
        double[] EndPointGeodesicDistances,
        out IntPtr OffsetCurves);
    private delegate VFResult vfOffsetCurveByStartPointAndEndPointBySymmetry(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        [MarshalAs(UnmanagedType.LPStr)] string CurveID,
        double[] StartPointGeodesicDistances,
        double[] EndPointGeodesicDistances,
        out IntPtr OffsetCurves);
    private delegate VFResult vfCreateSurfaceByFill(
        IntPtr[] CurveIds,
        [MarshalAs(UnmanagedType.LPStr)] string Surface1,
        out IntPtr Surface2);
    private delegate VFResult vfDataTableSave([MarshalAs(UnmanagedType.BStr)] string dbPath);
    private delegate VFResult vfDataTableRetrive([MarshalAs(UnmanagedType.BStr)] string dbPath);
    private delegate VFResult vfTestPointAndBoundary(
        double x,
        double y,
        double z,
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        [MarshalAs(UnmanagedType.LPStr)] string BoundaryID,
        out int result);
    private delegate VFResult vfCreatGeodesicCurvebyPoint(
        [MarshalAs(UnmanagedType.LPStr)] string BoundaryID,
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        double px, double py, double pz,
        double dx, double dy, double dz,
        [MarshalAs(UnmanagedType.BStr)] string GeodesicCurveName,
        [MarshalAs(UnmanagedType.BStr)] string GeometricSetName,
        out IntPtr NewCurveID);

    private delegate VFResult vfGetBoundaryIDbyDivide(
        [MarshalAs(UnmanagedType.LPStr)] string OriginalBoundaryID,
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        double px, double py, double pz,
        [MarshalAs(UnmanagedType.LPStr)] string DivideCurve1ID,
        [MarshalAs(UnmanagedType.LPStr)] string? DivideCurve2ID,
        out IntPtr NewBoundaryID);
    private delegate VFResult vfSaveasStp(
        [MarshalAs(UnmanagedType.BStr)] string StpName,
        [MarshalAs(UnmanagedType.BStr)] string StpPath);
    private delegate VFResult vfGetBoundaryIDbyPoint(
        [MarshalAs(UnmanagedType.LPStr)] string OriginalBoundaryID,
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        double ptX, double ptY, double ptZ,
        IntPtr[] DivideCurveID,
        out IntPtr NewBoundaryID,
        out IntPtr NewSurfaceID);
    private delegate VFResult vfCreateSlitDart(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        [MarshalAs(UnmanagedType.LPStr)] string OriginalBoundaryID,
        double length, double ptX1, double ptY1, double ptZ1, double ptX2, double ptY2, double ptZ2,
        out IntPtr NewBoundaryID);
    private delegate VFResult vfCreateVShapeDart(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        [MarshalAs(UnmanagedType.LPStr)] string OriginalBoundaryID,
        double ptX1, double ptY1, double ptZ1, double ptX2, double ptY2, double ptZ2, double ptX3, double ptY3, double ptZ3,
        out IntPtr NewBoundaryID);
    private delegate VFResult vfGetLinesColorAndWidth(IntPtr[] Lines, out IntPtr R, out IntPtr G, out IntPtr B, out IntPtr Linewidth);
    private delegate VFResult vfChangeLinesColorAndWidth(IntPtr[] Lines, int[] R, int[] G, int[] B, double[] Linewidth);
    private delegate VFResult vfTestCurvesCoincidence(
        [MarshalAs(UnmanagedType.LPStr)] string Curve1ID,
        [MarshalAs(UnmanagedType.LPStr)] string Curve2ID,
        out int result);
    private delegate VFResult vfCreateJoin(IntPtr[] OriginalCurves, out IntPtr JoinCurve);
    private delegate VFResult vfGetActiveDocumentPath([MarshalAs(UnmanagedType.BStr)] out string ActiveDocumentPath);
    private delegate VFResult vfGetActiveDocumentType([MarshalAs(UnmanagedType.BStr)] out string ActiveDocumentType);
    private delegate VFResult vfPickPointCoord(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        int PickNums,
        out IntPtr X,
        out IntPtr Y,
        out IntPtr Z);
    private delegate VFResult vfGetNewBoundary(
        [MarshalAs(UnmanagedType.LPStr)] string OriginalCurveID,
        double x, double y, double z,
        [MarshalAs(UnmanagedType.LPStr)] string DivideCurveID,
        out IntPtr NewBoundaryID);
    private delegate VFResult vfSurfacesJointAndGetBoundary(
        IntPtr[] Surfaces,
        out IntPtr NewBoundaryID);
    private delegate VFResult vfTestCoincideSurface(
        [MarshalAs(UnmanagedType.LPStr)] string Surface1,
        [MarshalAs(UnmanagedType.LPStr)] string Surface2,
        out IntPtr CoincideLine,
        out int result);
    private delegate VFResult vfGetPointDirSurfaceIntersections(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        double Px,
        double Py,
        double Pz,
        double Dx,
        double Dy,
        double Dz,
        out int PointNumber,
        out IntPtr X,
        out IntPtr Y,
        out IntPtr Z);
    private delegate VFResult vfCreateNewDartBoundary(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        [MarshalAs(UnmanagedType.LPStr)] string OriginalBoundaryID,
        [MarshalAs(UnmanagedType.LPStr)] string NewVshapeID,
        double ptX1,
        double ptY1,
        double ptZ1,
        out IntPtr NewBoundaryID);
    private delegate VFResult vfCreateCurveOnSurfaceByTwoPoints3(
        double[] X1,
        double[] Y1,
        double[] Z1,
        double[] X2,
        double[] Y2,
        double[] Z2,
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        out IntPtr CurveID); // Note: const char**&
    private delegate VFResult vfCreateLine(double x1, double y1, double z1, double x2, double y2, double z2, out IntPtr LineID);
    private delegate VFResult vfSurfacesJoint(
        IntPtr[] Surfaces,
        out IntPtr NewSurfaceID);
    private delegate VFResult vfCreateSpliteCurve(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        [MarshalAs(UnmanagedType.LPStr)] string CurveID,
        [MarshalAs(UnmanagedType.LPStr)] string BoundaryID,
        out IntPtr SpliteCurveID);
    private delegate VFResult vfTestCurveOnSurface(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        [MarshalAs(UnmanagedType.LPStr)] string CurveRingID,
        [MarshalAs(UnmanagedType.LPStr)] string CurveID,
        out int result);
    private delegate VFResult vfTestTypeOfSurfaceAndGetGeneratrixVector(
        [MarshalAs(UnmanagedType.LPStr)] string SurfaceID,
        double x1,
        double y1,
        double z1,
        double x2,
        double y2,
        double z2,
        out double X,
        out double Y,
        out double Z,
        out int result);

    #region VF2 api
    private delegate VFResult vf2Create3DSection(
        [MarshalAs(UnmanagedType.LPStr)] string strLaminateName,
        [MarshalAs(UnmanagedType.LPStr)] string strCrossSectionCurve,
        [MarshalAs(UnmanagedType.LPStr)] string strComponentType,
        [MarshalAs(UnmanagedType.LPStr)] string strBoundaryType,
        [MarshalAs(UnmanagedType.LPStr)] string strScaleType,
        double dPlyOrLayerScale,
        double dCoreScale,
        [MarshalAs(UnmanagedType.LPStr)] string strVisStyle,
        [MarshalAs(UnmanagedType.LPStr)] string strVisProfileType,
        double dMinDistance,
        double dCorssSectionOffset,
        bool bCreateCrossSectionGeometry,
        [MarshalAs(UnmanagedType.LPStr)] string strOutputCurveType,
        bool bGeometryLocked,
        [MarshalAs(UnmanagedType.LPStr)] string strDBPath);
    private delegate VFResult vf2Create3DNotes(
        [MarshalAs(UnmanagedType.LPStr)] string str3DNotesName,
        [MarshalAs(UnmanagedType.LPStr)] string strLaminate,
        [MarshalAs(UnmanagedType.LPStr)] string strRosette,
        [MarshalAs(UnmanagedType.LPStr)] string strRefOrigin,
        bool bAnnotationLocked,
        [MarshalAs(UnmanagedType.LPStr)] string strBoundaryType,
        [MarshalAs(UnmanagedType.LPStr)] string strAnnotationLeader,
        [MarshalAs(UnmanagedType.LPStr)] string strSummaryInfo,
        [MarshalAs(UnmanagedType.LPStr)] string strComponentInfo,
        [MarshalAs(UnmanagedType.LPStr)] string strHeaderText,
        [MarshalAs(UnmanagedType.LPStr)] string strColumnSeparator,
        int iLinesPerColumn,
        bool bReverseSequence,
        [MarshalAs(UnmanagedType.LPStr)] string strDBPath);

    #endregion

    #region api #1
    private delegate VFResult vfGetEntityType(long IID, out GeomEntityType enType);
    #endregion

    #region debugging special
    // For error info
    private delegate VfLastErrorNative vfGetLastError();
    private delegate VFResult vfDisplayArrow(int pointId, double[] dir);

    // For MBD
    private delegate IntPtr dbgCreateMBDNode(
        int nodeType,
        [MarshalAs(UnmanagedType.LPStr)] string nodeName,
        [MarshalAs(UnmanagedType.LPStr)] string nodeKey,
        [MarshalAs(UnmanagedType.LPStr)] string nodeValue);
    // consume child node
    private delegate void dbgAppendChild(IntPtr parentNode, IntPtr childNode);
    private delegate void dbgFreeNode(IntPtr node);
    // deep copy, not consuming
    private delegate VFResult dbgCreateMBD(IntPtr tree1, IntPtr tree2);

    #endregion
    #endregion

    #region Wrappers
    public void StartLinkWithCAD(int type)
    {
        var function = Library.GetFunction<vfStartLinkWithCAD>(nameof(vfStartLinkWithCAD));
        var result = function(type);
        ThrowIfError(result);
    }

    public string SelectionToId(string filter)
    {
        var function = Library.GetFunction<vfSelectionToID>(nameof(vfSelectionToID));
        var outputBuf = new StringBuilder(256);
        var result = function(filter, outputBuf);
        ThrowIfError(result);
        return outputBuf.ToString();
    }

    public void ChangeLineColorWidthInsertMap(string[] entities)
    {
        var function = Library.GetFunction<vfChangeLineColorWidthInsertMap>(nameof(vfChangeLineColorWidthInsertMap));
        IntPtr[]? inputArr = null;
        try
        {
            inputArr = StringArrayToPtrArray(entities);
            var result = function(inputArr);
            ThrowIfError(result);
        }
        finally
        {
            FreePtrArray(inputArr);
        }
    }

    public void ChangeLineColorWidthFromMap(string[] entities)
    {
        var function = Library.GetFunction<vfChangeLineColorWidthFromMap>(nameof(vfChangeLineColorWidthFromMap));
        IntPtr[]? inputArr = null;
        try
        {
            inputArr = StringArrayToPtrArray(entities);
            var result = function(inputArr);
            ThrowIfError(result);
        }
        finally
        {
            FreePtrArray(inputArr);
        }
    }

    public string CurveCurveIntersection(string curveId1, string curveId2, out Vector3d coordinate)
    {
        var function = Library.GetFunction<vfCurveCurveIntersection>(nameof(vfCurveCurveIntersection));
        coordinate = new();
        var result = function(curveId1, curveId2, out coordinate.x, out coordinate.y, out coordinate.z, out var pointIdStr);
        ThrowIfError(result);
        string pointId = Marshal.PtrToStringAnsi(pointIdStr) ?? "";
        return pointId;
    }

    public string CurveSurfaceIntersection(string curveId, string surfaceId, out Vector3d coordinate)
    {
        var function = Library.GetFunction<vfCurveSurfaceIntersection>(nameof(vfCurveSurfaceIntersection));
        coordinate = new();
        var result = function(curveId, surfaceId, out coordinate.x, out coordinate.y, out coordinate.z, out var pointIdStr);
        ThrowIfError(result);
        string pointId = Marshal.PtrToStringAnsi(pointIdStr) ?? "";
        return pointId;
    }

    public double SurfaceArea(string surfaceId)
    {
        var function = Library.GetFunction<vfSurfaceArea>(nameof(vfSurfaceArea));
        var result = function(surfaceId, out var area);
        ThrowIfError(result);
        return area;
    }

    public double SurfacePerimeter(string surfaceId)
    {
        var function = Library.GetFunction<vfSurfacePerimeter>(nameof(vfSurfacePerimeter));
        var result = function(surfaceId, out var perimeter);
        ThrowIfError(result);
        return perimeter;
    }

    public double SurfaceWeight(string surfaceId, double density)
    {
        var function = Library.GetFunction<vfSurfaceWeight>(nameof(vfSurfaceWeight));
        var result = function(surfaceId, density, out var weight);
        ThrowIfError(result);
        return weight;
    }

    public Vector3d GravityOfSurface(string surfaceId)
    {
        var function = Library.GetFunction<vfCreateGravityOfSurface>(nameof(vfCreateGravityOfSurface));
        Vector3d retval = new();
        var result = function(surfaceId, out retval.x, out retval.y, out retval.z);
        ThrowIfError(result);
        return retval;
    }

    public bool TestPointOnCurve(string pointId, string curveId)
    {
        var function = Library.GetFunction<vfTestPointOnCurve>(nameof(vfTestPointOnCurve));
        var result = function(pointId, curveId, out bool retval);
        ThrowIfError(result);
        return retval;
    }

    public int TestPointOnSurface(string pointId, string surfaceId)
    {
        var function = Library.GetFunction<vfTestPointOnSurface>(nameof(vfTestPointOnSurface));
        var result = function(pointId, surfaceId, out var retval);
        ThrowIfError(result);
        return retval;
    }

    public int TestPointOnSurface(Vector3d point, string surfaceId)
    {
        var function = Library.GetFunction<vfTestPointOnSurface2>(nameof(vfTestPointOnSurface2));
        var result = function(point.x, point.y, point.z, surfaceId, out var retval);
        ThrowIfError(result);
        return retval;
    }

    public string CreateCurveBySurfaceIntersection(string surface1, string surface2)
    {
        var function = Library.GetFunction<vfCreateCurveBySurfaceIntersection>(nameof(vfCreateCurveBySurfaceIntersection));
        var result = function(surface1, surface2, out var curveId);
        ThrowIfError(result);
        return Marshal.PtrToStringAnsi(curveId) ?? "";
    }

    public double GetCurveLength(string curveId)
    {
        var function = Library.GetFunction<vfCurveLength>(nameof(vfCurveLength));
        var result = function(curveId, out var length);
        ThrowIfError(result);
        return length;
    }

    public Vector3d GetUnitTangentVectorAtPoint(string curveId, string pointId)
    {
        var function = Library.GetFunction<vfGetUnitTangentVectorAtPoint>(nameof(vfGetUnitTangentVectorAtPoint));
        Vector3d retval = new();
        var result = function(curveId, pointId, 0, out retval.x, out retval.y, out retval.z);
        ThrowIfError(result);
        return retval;
    }

    public Vector3d GetUnitNormalVectorAtPoint(string surfaceId, string pointId)
    {
        var function = Library.GetFunction<vfGetUnitNormalVectorAtPoint>(nameof(vfGetUnitNormalVectorAtPoint));
        Vector3d retval = new();
        var result = function(surfaceId, pointId, 0, out retval.x, out retval.y, out retval.z);
        ThrowIfError(result);
        return retval;
    }

    public void CreateNormalLineAndArrowHead(string surfaceId, string pointId)
    {
        var function = Library.GetFunction<vfCreateNormalLineAndArrowhead>(nameof(vfCreateNormalLineAndArrowhead));
        var result = function(surfaceId, pointId);
        ThrowIfError(result);
    }

    public void DeleteNormalLineAndArrowhead(string pointId)
    {
        var function = Library.GetFunction<vfDeleteNormalLineAndArrowhead>(nameof(vfDeleteNormalLineAndArrowhead));
        var result = function(pointId);
        ThrowIfError(result);
    }

    public int TestPointOnSurfaceByProject(string surfaceId, Vector3d point, Vector3d direction)
    {
        var function = Library.GetFunction<vfTestPointOnSurfaceByProject>(nameof(vfTestPointOnSurfaceByProject));
        var result = function(surfaceId, point.x, point.y, point.z, direction.x, direction.y, direction.z, out var retval);
        ThrowIfError(result);
        return retval;
    }

    public Vector3d GetPointCoord(string pointId)
    {
        var function = Library.GetFunction<vfGetCoordOfPoint>(nameof(vfGetCoordOfPoint));
        Vector3d retval = new();
        var result = function(pointId, out retval.x, out retval.y, out retval.z);
        ThrowIfError(result);
        return retval;
    }

    public void CreateSplitOrTrim(string curve1, string curve2, string cutType, int orientation1, int orientation2)
    {
        var function = Library.GetFunction<vfCreateSplitOrTrim>(nameof(vfCreateSplitOrTrim));
        var result = function(curve1, curve2, cutType, orientation1, orientation2);
        ThrowIfError(result);
    }

    public string OffsetSurface(string surfaceId, double dist)
    {
        var function = Library.GetFunction<vfOffsetSurface>(nameof(vfOffsetSurface));
        var result = function(surfaceId, "0", dist, out var surfaceID2);
        string retval = Marshal.PtrToStringAnsi(surfaceID2) ?? "";
        ThrowIfError(result);
        return retval;
    }

    public List<string> GetIntersectCurve(string curveId)
    {
        var function = Library.GetFunction<vfGetIntersectCurves>(nameof(vfGetIntersectCurves));
        List<string> retval = [];
        var result = function(curveId, out var intersectCurveID);
        ThrowIfError(result);
        while (true)
        {
            var strptr = Marshal.ReadIntPtr(intersectCurveID);
            if (strptr == IntPtr.Zero)
            {
                break;
            }
            string str = Marshal.PtrToStringAnsi(strptr) ?? "";
            retval.Add(str);
            intersectCurveID += IntPtr.Size;
        }
        return retval;
    }

    public List<string> MultiSelect(string filter)
    {
        var function = Library.GetFunction<vfSelectionToIDMore>(nameof(vfSelectionToIDMore));
        List<string> retval = [];
        var result = function(filter, out var pickIds);
        ThrowIfError(result);
        while (true)
        {
            var strptr = Marshal.ReadIntPtr(pickIds);
            if (strptr == IntPtr.Zero)
            {
                break;
            }
            string str = Marshal.PtrToStringAnsi(strptr) ?? "";
            retval.Add(str);
            pickIds += IntPtr.Size;
        }
        return retval;
    }

    public string AddCommentOnPoint(string surfaceId, Vector3d location, string comment, Color color, string Font, double size)
    {
        var function = Library.GetFunction<vfAddCommentOnPoint>(nameof(vfAddCommentOnPoint));
        var result = function(surfaceId, location.x, location.y, location.z, comment, color.R, color.G, color.B, Font, size, out var outputID);
        ThrowIfError(result);
        return Marshal.PtrToStringAnsi(outputID) ?? "";
    }

    public void RemoveCommentOnPoint(string pointID)
    {
        var function = Library.GetFunction<vfDeleteCommentOnPoint>(nameof(vfDeleteCommentOnPoint));
        var result = function(pointID);
        ThrowIfError(result);
    }

    public void ChangePointColorWidthInsertMap(string[] entities, int red, int green, int blue)
    {
        var function = Library.GetFunction<vfChangePointColorInsertMap>(nameof(vfChangePointColorInsertMap));
        IntPtr[]? inputArr = null;
        try
        {
            inputArr = StringArrayToPtrArray(entities);
            var result = function(inputArr, red, green, blue);
            ThrowIfError(result);
        }
        finally
        {
            FreePtrArray(inputArr);
        }
    }

    public void ChangePointColorWidthFromMap(string[] entities)
    {
        var function = Library.GetFunction<vfChangePointColorFromMap>(nameof(vfChangePointColorFromMap));
        IntPtr[]? inputArr = null;
        try
        {
            inputArr = StringArrayToPtrArray(entities);
            var result = function(inputArr);
            ThrowIfError(result);
        }
        finally
        {
            FreePtrArray(inputArr);
        }
    }

    public void CreateCurveOnSurfaceByTwoPoints(string surfaceId, IEnumerable<Vector3d> startPoints, IEnumerable<Vector3d> endPoints)
    {
        var (startX, startY, startZ) = SplitVectorList(startPoints);
        var (endX, endY, endZ) = SplitVectorList(endPoints);
        var function = Library.GetFunction<vfCreateCurveOnSurfaceByTwoPoints>(nameof(vfCreateCurveOnSurfaceByTwoPoints));
        var result = function(startX, startY, startZ, endX, endY, endZ, surfaceId);
        ThrowIfError(result);
    }

    public double GeodesicLengthOfPointToCurve(string SurfaceID, string CurveID, string PointID, Vector3d direction)
    {
        var function = Library.GetFunction<vfGeodesicLengthOfPointToCurve>(nameof(vfGeodesicLengthOfPointToCurve));
        var result = function(SurfaceID, CurveID, PointID, direction.x, direction.y, direction.z, out var retval);
        ThrowIfError(result);
        return retval;
    }

    public Vector3d GetPointByGeodesicDis(string SurfaceID, Vector3d startPoint, Vector3d direction, double distance)
    {
        var function = Library.GetFunction<vfGetPointByGeodesicDis>(nameof(vfGetPointByGeodesicDis));
        var result = function(SurfaceID, startPoint.x, startPoint.y, startPoint.z, direction.x, direction.y, direction.z, distance,
            out var outputx, out var outputy, out var outputz, out var isSuccess);
        ThrowIfError(result);
        return new Vector3d() { x = outputx, y = outputy, z = outputz };
    }

    public void ChangeSurfaceColorInsertMap(string[] surfaces, int[] red, int[] green, int[] blue)
    {
        var func = Library.GetFunction<vfChangeSurfaceColorInsertMap>(nameof(vfChangeSurfaceColorInsertMap));
        IntPtr[]? inputArr = null;
        try
        {
            inputArr = StringArrayToPtrArray(surfaces);
            var result = func(inputArr, red, green, blue);
            ThrowIfError(result);
        }
        finally
        {
            FreePtrArray(inputArr);
        }
    }

    public void ChangeSurfaceColorFromMap(string[] surfaces)
    {
        var func = Library.GetFunction<vfChangeSurfaceColorFromMap>(nameof(vfChangeSurfaceColorFromMap));
        IntPtr[]? inputArr = null;
        try
        {
            inputArr = StringArrayToPtrArray(surfaces);
            var result = func(inputArr);
            ThrowIfError(result);
        }
        finally
        {
            FreePtrArray(inputArr);
        }
    }

    public string CreatePointByProjection(string pointId, string surfaceId)
    {
        var function = Library.GetFunction<vfCreatePointByProjection>(nameof(vfCreatePointByProjection));
        var result = function(pointId, surfaceId, out var newPointId);
        string retval = Marshal.PtrToStringAnsi(newPointId) ?? "";
        ThrowIfError(result);
        return retval;
    }

    public string GetActiveDocumentName()
    {
        var function = Library.GetFunction<vfGetActiveDocumentName>(nameof(vfGetActiveDocumentName));
        var result = function(out var docName);
        ThrowIfError(result);
        return docName;
    }

    public (Vector3d, Vector3d) GetCurveEndPoints(string curveId)
    {
        var function = Library.GetFunction<vfGetCurveStartAndEndPointCoordinates>(nameof(vfGetCurveStartAndEndPointCoordinates));
        Vector3d p0 = new(), p1 = new();
        var result = function(curveId, out p0.x, out p0.y, out p0.z, out p1.x, out p1.y, out p1.z);
        ThrowIfError(result);
        return (p0, p1);
    }

    public Vector3d[] GetEquidistantPointsOnCurve(string curveId, int numPoints)
    {
        var function = Library.GetFunction<vfGetEquidistantPointsOnCurve>(nameof(vfGetEquidistantPointsOnCurve));
        var result = function(curveId, numPoints, out var arrX, out var arrY, out var arrZ);
        ThrowIfError(result);
        // convert X,Y,Z double* into Vector3d[]
        Vector3d[] points = ReadVectorFromNative(numPoints, arrX, arrY, arrZ);
        return points;
    }

    public Vector3d[] GetEquidistantPointsOnCurveRing(string curveId, int numPoints)
    {
        var function = Library.GetFunction<vfGetEquidistantPointsOnCurveRing>(nameof(vfGetEquidistantPointsOnCurveRing));
        var result = function(curveId, numPoints, out var arrX, out var arrY, out var arrZ);
        ThrowIfError(result);
        // convert X,Y,Z double* into Vector3d[]
        Vector3d[] points = new Vector3d[numPoints];
        unsafe
        {
            Span<double> arrXspan = new((void*)arrX, numPoints);
            Span<double> arrYspan = new((void*)arrY, numPoints);
            Span<double> arrZspan = new((void*)arrZ, numPoints);
            for (int i = 0; i < numPoints; i++)
            {
                points[i] = new Vector3d { x = arrXspan[i], y = arrYspan[i], z = arrZspan[i] };
            }
        }
        return points;
    }

    public void DismantleCurve(string curveId, out string[] segments, out string[] type)
    {
        var function = Library.GetFunction<vfDismantleCurve>(nameof(vfDismantleCurve));
        var result = function(curveId, out var arrSegments, out var arrType, out var length);
        ThrowIfError(result);
        segments = new string[length];
        type = new string[length];
        for (int i = 0; i < length; i++)
        {
            var segmentI = Marshal.ReadIntPtr(arrSegments, i * IntPtr.Size);
            var arrTypeI = Marshal.ReadIntPtr(arrType, i * IntPtr.Size);
            segments[i] = Marshal.PtrToStringAnsi(segmentI) ?? "";
            type[i] = Marshal.PtrToStringAnsi(arrTypeI) ?? "";
        }
    }

    public Vector3d GetMinimumDistancePointCoordinates(string surfaceId, Vector3d point)
    {
        var function = Library.GetFunction<vfGetMinimumDistancePointCoordinates>(nameof(vfGetMinimumDistancePointCoordinates));
        var result = function(surfaceId, point.x, point.y, point.z, out var x, out var y, out var z);
        ThrowIfError(result);
        return new Vector3d { x = x, y = y, z = z };
    }

    public void CreateSpline2d(string name, int plane, Vector2d[] points)
    {
        var function = Library.GetFunction<vfCreateSpline2D>(nameof(vfCreateSpline2D));
        double[] coord1 = [.. points.Select(p => p.x)];
        double[] coord2 = [.. points.Select(p => p.y)];

        var result = function(name, plane, points.Length, coord1, coord2);
        ThrowIfError(result);
    }

    public void DeleteCurves(string[] curves)
    {
        var function = Library.GetFunction<vfDeleteCurves>(nameof(vfDeleteCurves));
        IntPtr[]? inputArr = null;
        try
        {
            inputArr = StringArrayToPtrArray(curves);
            var result = function(inputArr);
            ThrowIfError(result);
        }
        finally
        {
            FreePtrArray(inputArr);
        }
    }

    public void DeleteSurfaces(string[] curves)
    {
        var function = Library.GetFunction<vfDeleteSurfaces>(nameof(vfDeleteSurfaces));
        IntPtr[]? inputArr = null;
        try
        {
            inputArr = StringArrayToPtrArray(curves);
            var result = function(inputArr);
            ThrowIfError(result);
        }
        finally
        {
            FreePtrArray(inputArr);
        }
    }

    public void TrimOrExtendCurvesFromEndPoint(string[] curves, string surface, Vector3d[] points, double[] offset)
    {
        var function = Library.GetFunction<vfTrimOrExtendCurvesFromEndPoint>(nameof(vfTrimOrExtendCurvesFromEndPoint));
        IntPtr[]? inputArr = null;
        try
        {
            inputArr = StringArrayToPtrArray(curves);
            var (x, y, z) = SplitVectorList(points);
            var result = function(inputArr, surface, x, y, z, offset);
            ThrowIfError(result);
        }
        finally
        {
            FreePtrArray(inputArr);
        }
    }

    public string[] OffsetCurvesByParallel(string surfaceId, string curveId, Vector3d dir, string GeosetName, string[] curvesName, double[] offset)
    {
        if (!double.IsNaN(offset[^1]))
        {
            offset = [.. offset, double.NaN];
        }

        var function = Library.GetFunction<vfOffsetCurvesByParallel>(nameof(vfOffsetCurvesByParallel));
        var curvesNameBstr = BSTRMarshaller.AllocateBSTRArray(curvesName);
        try
        {
            var result = function(surfaceId, curveId, dir.x, dir.y, dir.z, offset.Length - 1, offset, GeosetName, curvesNameBstr.NativeArrayPtr, out var offsetCurves);
            ThrowIfError(result);
            return [.. ReadStrArray(offsetCurves)];
        }
        finally
        {
            BSTRMarshaller.ReleaseBSTRArray(ref curvesNameBstr);
        }
    }

    public string[] OffsetCurvesByParallel2(string surfaceId, string curveId, Vector3d dir, string GeosetName, string[] curvesName, double[] offset)
    {
        if (!double.IsNaN(offset[^1]))
        {
            offset = [.. offset, double.NaN];
        }

        var function = Library.GetFunction<vfOffsetCurvesByParallel2>(nameof(vfOffsetCurvesByParallel2));
        var curvesNameBstr = BSTRMarshaller.AllocateBSTRArray(curvesName);
        try
        {
            var result = function(surfaceId, curveId, dir.x, dir.y, dir.z, offset.Length - 1, offset, GeosetName, curvesNameBstr.NativeArrayPtr, out var offsetCurves);
            ThrowIfError(result);
            return [.. ReadStrArray(offsetCurves)];
        }
        finally
        {
            BSTRMarshaller.ReleaseBSTRArray(ref curvesNameBstr);
        }
    }

    public string[] OffsetCurvesBySymmetry(string surfaceId, string curveId, double[] offset)
    {
        if (!double.IsNaN(offset[^1]))
        {
            offset = [.. offset, double.NaN];
        }

        var function = Library.GetFunction<vfOffsetCurvesBySymmetry>(nameof(vfOffsetCurvesBySymmetry));
        var result = function(surfaceId, curveId, offset, out var offsetCurves);
        ThrowIfError(result);
        return [.. ReadStrArray(offsetCurves)];
    }

    public string[] OffsetCurvesByStartPointAndEndPoint(string surfaceId, string curveId, string orientation, double[] startoffset, double[] endoffset)
    {
        if (!double.IsNaN(startoffset[^1]))
        {
            startoffset = [.. startoffset, double.NaN];
        }
        if (!double.IsNaN(endoffset[^1]))
        {
            endoffset = [.. endoffset, double.NaN];
        }

        var function = Library.GetFunction<vfOffsetCurveByStartPointAndEndPoint>(nameof(vfOffsetCurveByStartPointAndEndPoint));
        var result = function(surfaceId, curveId, orientation, startoffset, endoffset, out var offsetCurves);
        ThrowIfError(result);
        return [.. ReadStrArray(offsetCurves)];
    }

    public string[] OffsetCurvesByStartPointAndEndPointBySymmetry(string surfaceId, string curveId, double[] startoffset, double[] endoffset)
    {
        if (!double.IsNaN(startoffset[^1]))
        {
            startoffset = [.. startoffset, double.NaN];
        }
        if (!double.IsNaN(endoffset[^1]))
        {
            endoffset = [.. endoffset, double.NaN];
        }

        var function = Library.GetFunction<vfOffsetCurveByStartPointAndEndPointBySymmetry>(nameof(vfOffsetCurveByStartPointAndEndPointBySymmetry));
        var result = function(surfaceId, curveId, startoffset, endoffset, out var offsetCurves);
        ThrowIfError(result);
        return [.. ReadStrArray(offsetCurves)];
    }

    public string CreateSurfaceByFill(string[] curveIds, string baseSurfaceId)
    {
        var function = Library.GetFunction<vfCreateSurfaceByFill>(nameof(vfCreateSurfaceByFill));
        IntPtr[]? curveIdsPtr = null;
        try
        {
            curveIdsPtr = StringArrayToPtrArray(curveIds);
            var result = function(curveIdsPtr, baseSurfaceId, out var surface2);
            ThrowIfError(result);
            return Marshal.PtrToStringAnsi(surface2) ?? "";
        }
        finally
        {
            FreePtrArray(curveIdsPtr);
        }
    }

    public void DataTableSave(string dbPath)
    {
        var function = Library.GetFunction<vfDataTableSave>(nameof(vfDataTableSave));
        var result = function(dbPath);
        ThrowIfError(result);
    }

    public void DataTableRetrieve(string dbPath)
    {
        var function = Library.GetFunction<vfDataTableRetrive>(nameof(vfDataTableRetrive));
        var result = function(dbPath);
        ThrowIfError(result);
    }

    public int TestPointAndBoundary(string boundaryId, string surfaceId, Vector3d point)
    {
        var function = Library.GetFunction<vfTestPointAndBoundary>(nameof(vfTestPointAndBoundary));
        var result = function(point.x, point.y, point.z, surfaceId, boundaryId, out int retval);
        ThrowIfError(result);
        return retval;
    }

    public string CreatGeodesicCurvebyPoint(string boundaryId, string surfaceId, Vector3d point, Vector3d dir, string curveName, string geometrySet)
    {
        var function = Library.GetFunction<vfCreatGeodesicCurvebyPoint>(nameof(vfCreatGeodesicCurvebyPoint));
        var result = function(boundaryId, surfaceId, point.x, point.y, point.z, dir.x, dir.y, dir.z, curveName, geometrySet, out var newCurveID);
        ThrowIfError(result);
        string retval = Marshal.PtrToStringAnsi(newCurveID) ?? string.Empty;
        return retval;
    }

    public string GetBoundaryIDbyDivide(string OriginalBoundaryID, string SurfaceID, Vector3d point, string DivideCurve1ID, string? DivideCurve2ID)
    {
        var function = Library.GetFunction<vfGetBoundaryIDbyDivide>(nameof(vfGetBoundaryIDbyDivide));
        var result = function(OriginalBoundaryID, SurfaceID, point.x, point.y, point.z, DivideCurve1ID, DivideCurve2ID, out var newBoundaryID);
        ThrowIfError(result);
        string retval = Marshal.PtrToStringAnsi(newBoundaryID) ?? string.Empty;
        return retval;
    }

    public (string boundary, string surface) GetBoundaryIDByPoint(string OriginalBoundaryID, string surfaceId, string[] boundaryCurves, Vector3d point)
    {
        var function = Library.GetFunction<vfGetBoundaryIDbyPoint>(nameof(vfGetBoundaryIDbyPoint));
        IntPtr[]? curveIdsPtr = null;
        try
        {
            curveIdsPtr = StringArrayToPtrArray(boundaryCurves);
            var result = function(OriginalBoundaryID, surfaceId, point.x, point.y, point.z, curveIdsPtr, out var newBoundaryID, out var newSurfaceID);
            ThrowIfError(result);
            var boundary_id = Marshal.PtrToStringAnsi(newBoundaryID) ?? "";
            var surface_id = Marshal.PtrToStringAnsi(newSurfaceID) ?? "";
            return (boundary_id, surface_id);
        }
        finally
        {
            FreePtrArray(curveIdsPtr);
        }
    }

    public void SaveAsStep(string name, string path)
    {
        var function = Library.GetFunction<vfSaveasStp>(nameof(vfSaveasStp));
        var result = function(name, path);
        ThrowIfError(result);
    }

    public string CreateSlitDart(string surfaceId, string loopId, double width, Vector3d p1, Vector3d p2)
    {
        var function = Library.GetFunction<vfCreateSlitDart>(nameof(vfCreateSlitDart));
        var result = function(surfaceId, loopId, width, p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, out var newCurveID);
        ThrowIfError(result);
        return Marshal.PtrToStringAnsi(newCurveID) ?? "";
    }

    public string CreateVshapeDart(string surfaceId, string loopId, Vector3d p1, Vector3d p2, Vector3d p3)
    {
        var function = Library.GetFunction<vfCreateVShapeDart>(nameof(vfCreateVShapeDart));
        var result = function(surfaceId, loopId, p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, out var newCurveID);
        ThrowIfError(result);
        return Marshal.PtrToStringAnsi(newCurveID) ?? "";
    }

    public void GetLinesColorAndWidth(string[] curves, out int[] red, out int[] green, out int[] blue, out double[] width)
    {
        red = new int[curves.Length];
        green = new int[curves.Length];
        blue = new int[curves.Length];
        width = new double[curves.Length];
        var func = Library.GetFunction<vfGetLinesColorAndWidth>(nameof(vfGetLinesColorAndWidth));
        IntPtr[]? curvesPtr = null;
        try
        {
            curvesPtr = StringArrayToPtrArray(curves);
            var result = func(curvesPtr, out var redptr, out var greenptr, out var blueptr, out var widthptr);
            ThrowIfError(result);
            Marshal.Copy(redptr, red, 0, curves.Length);
            Marshal.Copy(greenptr, green, 0, curves.Length);
            Marshal.Copy(blueptr, blue, 0, curves.Length);
            Marshal.Copy(widthptr, width, 0, curves.Length);
        }
        finally
        {
            FreePtrArray(curvesPtr);
        }
    }

    public void ChangeLinesColorAndWidth(string[] curves, int[] red, int[] green, int[] blue, double[] width)
    {
        var func = Library.GetFunction<vfChangeLinesColorAndWidth>(nameof(vfChangeLinesColorAndWidth));
        IntPtr[]? curvesPtr = null;
        try
        {
            curvesPtr = StringArrayToPtrArray(curves);
            var result = func(curvesPtr, red, green, blue, width);
            ThrowIfError(result);
        }
        finally
        {
            FreePtrArray(curvesPtr);
        }
    }

    public int TestCurveCoincidence(string curve1, string curve2)
    {
        var func = Library.GetFunction<vfTestCurvesCoincidence>(nameof(vfTestCurvesCoincidence));
        var result = func(curve1, curve2, out var retval);
        ThrowIfError(result);
        return retval;
    }

    public string CreateJoin(string[] curves)
    {
        var func = Library.GetFunction<vfCreateJoin>(nameof(vfCreateJoin));
        IntPtr[]? curvesPtr = null;
        try
        {
            curvesPtr = StringArrayToPtrArray(curves);
            var result = func(curvesPtr, out var newCurveID);
            ThrowIfError(result);
            return Marshal.PtrToStringAnsi(newCurveID) ?? "";
        }
        finally
        {
            FreePtrArray(curvesPtr);
        }
    }

    public string GetActiveDocumentPath()
    {
        var func = Library.GetFunction<vfGetActiveDocumentPath>(nameof(vfGetActiveDocumentPath));
        var result = func(out var path);
        ThrowIfError(result);
        return path;
    }

    public string GetActiveDocumentType()
    {
        var func = Library.GetFunction<vfGetActiveDocumentType>(nameof(vfGetActiveDocumentType));
        var result = func(out var type);
        ThrowIfError(result);
        return type;
    }

    public Vector3d[] PickPointCoord(string surface, int n)
    {
        var func = Library.GetFunction<vfPickPointCoord>(nameof(vfPickPointCoord));
        var result = func(surface, n, out var x, out var y, out var z);
        ThrowIfError(result);
        return ReadVectorFromNative(n, x, y, z);
    }

    public string GetNewBoundary(string original, string additionalPart, Vector3d hintPoint)
    {
        var function = Library.GetFunction<vfGetNewBoundary>(nameof(vfGetNewBoundary));
        var result = function(original, hintPoint.x, hintPoint.y, hintPoint.z, additionalPart, out var newBoundaryID);
        ThrowIfError(result);
        var newBoundary = Marshal.PtrToStringAnsi(newBoundaryID);
        return newBoundary ?? "";
    }

    public string SurfaceJointAndGetBoundary(string[] surfaces)
    {
        var function = Library.GetFunction<vfSurfacesJointAndGetBoundary>(nameof(vfSurfacesJointAndGetBoundary));
        IntPtr[]? inputArr = null;
        try
        {
            inputArr = StringArrayToPtrArray(surfaces);
            var result = function(inputArr, out var newBoundaryID);
            ThrowIfError(result);
            return Marshal.PtrToStringAnsi(newBoundaryID) ?? "";
        }
        finally
        {
            FreePtrArray(inputArr);
        }
    }

    public int TestCoincideSurface(string surface1, string surface2, out string coincideLine)
    {
        var func = Library.GetFunction<vfTestCoincideSurface>(nameof(vfTestCoincideSurface));
        var result = func(surface1, surface2, out var coincideLineId, out var retval);
        ThrowIfError(result);
        coincideLine = Marshal.PtrToStringAnsi(coincideLineId) ?? "";
        return retval;
    }

    public Vector3d[] GetPointDirSurfaceIntersections(string surface, Vector3d point, Vector3d dir)
    {
        var func = Library.GetFunction<vfGetPointDirSurfaceIntersections>(nameof(vfGetPointDirSurfaceIntersections));
        var result = func(surface, point.x, point.y, point.z, dir.x, dir.y, dir.z, out var n, out var x, out var y, out var z);
        ThrowIfError(result);
        return ReadVectorFromNative(n, x, y, z);
    }

    public string CreateNewDartBoundary(string surface, string originalBoundary, string newShape, Vector3d hintPoint)
    {
        var function = Library.GetFunction<vfCreateNewDartBoundary>(nameof(vfCreateNewDartBoundary));
        var result = function(surface, originalBoundary, newShape, hintPoint.x, hintPoint.y, hintPoint.z, out var newBoundaryID);
        ThrowIfError(result);
        return Marshal.PtrToStringAnsi(newBoundaryID) ?? "";
    }

    public List<string> CreateCurveOnSurfaceByTwoPoints3(string surfaceId, IEnumerable<Vector3d> startPoints, IEnumerable<Vector3d> endPoints)
    {

        var (startX, startY, startZ) = SplitVectorList(startPoints);
        var (endX, endY, endZ) = SplitVectorList(endPoints);
        var function = Library.GetFunction<vfCreateCurveOnSurfaceByTwoPoints3>(nameof(vfCreateCurveOnSurfaceByTwoPoints3));
        var result = function(startX, startY, startZ, endX, endY, endZ, surfaceId, out var curveID);
        ThrowIfError(result);
        return ReadStrArray(curveID);
    }

    public string CreateLine(Vector3d p1, Vector3d p2)
    {
        var function = Library.GetFunction<vfCreateLine>(nameof(vfCreateLine));
        var result = function(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, out var newCurveID);
        ThrowIfError(result);
        return Marshal.PtrToStringAnsi(newCurveID) ?? "";
    }

    public string SurfaceJoint(string[] surfaces)
    {
        var function = Library.GetFunction<vfSurfacesJoint>(nameof(vfSurfacesJoint));
        IntPtr[]? inputArr = null;
        try
        {
            inputArr = StringArrayToPtrArray(surfaces);
            var result = function(inputArr, out var newSurfaceId);
            ThrowIfError(result);
            return Marshal.PtrToStringAnsi(newSurfaceId) ?? "";
        }
        finally
        {
            FreePtrArray(inputArr);
        }
    }

    public string CreateSpliteCurve(string surface, string curve, string loop)
    {
        var function = Library.GetFunction<vfCreateSpliteCurve>(nameof(vfCreateSpliteCurve));
        var result = function(surface, curve, loop, out IntPtr spliteCurveID);
        ThrowIfError(result);
        return Marshal.PtrToStringAnsi(spliteCurveID) ?? "";
    }

    public int TestCurveOnSurface(string surface, string loop, string curve)
    {
        var function = Library.GetFunction<vfTestCurveOnSurface>(nameof(vfTestCurveOnSurface));
        var result = function(surface, loop, curve, out int relpos);
        ThrowIfError(result);
        return relpos;
    }

    public (int, Vector3d) TestTypeOfSurfaceAndGetGeneratrixVector(string surface, Vector3d p1, Vector3d p2)
    {
        var function = Library.GetFunction<vfTestTypeOfSurfaceAndGetGeneratrixVector>(nameof(vfTestTypeOfSurfaceAndGetGeneratrixVector));
        Vector3d generatrix = new();
        var result = function(surface, p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, out generatrix.x, out generatrix.y, out generatrix.z, out var retval);
        ThrowIfError(result);
        return (retval, generatrix);
    }

    public void CreateMBDTree(MBDTreeNode tree1, MBDTreeNode tree2)
    {
        IntPtr tree1Ptr = IntPtr.Zero;
        IntPtr tree2Ptr = IntPtr.Zero;

        try
        {
            tree1Ptr = MapDataToNative(tree1);
            tree2Ptr = MapDataToNative(tree2);
            var function = Library.GetFunction<dbgCreateMBD>(nameof(dbgCreateMBD));
            var result = function(tree1Ptr, tree2Ptr);
            ThrowIfError(result);
        }
        finally
        {
            var freeFunc = Library.GetFunction<dbgFreeNode>(nameof(dbgFreeNode));
            freeFunc(tree1Ptr);
            freeFunc(tree2Ptr);
        }
    }

    #region VF2 api export

    public record PlySectionInput
    (
        string LaminateName = "",
        string CrossSectionCurveId = "",
        string ComponentType = "",
        string BoundaryType = "",
        string ScaleType = "",
        double Scale = 1.0,
        double CoreScale = 1.0,
        string VisStype = "",
        string VisProfileType = "",
        double MinDistance = 0.0,
        double CrossSectionOffset = 0.0,
        bool CreateGeometry = true,
        string OutputCurveType = "",
        bool Locked = false,
        string DbPath = ""
    );

    public void Create3DSection(PlySectionInput input)
    {
        var function = Library.GetFunction<vf2Create3DSection>(nameof(vf2Create3DSection));
        var result = function(input.LaminateName, input.CrossSectionCurveId, input.ComponentType,
            input.BoundaryType, input.ScaleType, input.Scale, input.CoreScale, input.VisStype,
            input.VisProfileType, input.MinDistance, input.CrossSectionOffset, input.CreateGeometry,
            input.OutputCurveType, input.Locked, input.DbPath);
        ThrowIfError(result);
    }

    public record Ply3DNoteInput(
        string NotesName = "",
        string LaminateName = "",
        string RosetteId = "",
        string RefOriginId = "",
        bool AnnotationLocked = false,
        string BoundaryType = "Net",
        string AnnotationLeader = "",
        string SummaryInfo = "Count/Thickness",
        string ComponentInfo = "Name/Seq-Step/Orient/Material",
        string HeaderText = "",
        string ColumnSeparator = "|",
        int LinesPerColumn = 0,
        bool ReverseSequence = false,
        string DbPath = ""
        );

    public void Create3DNotes(Ply3DNoteInput input)
    {
        var function = Library.GetFunction<vf2Create3DNotes>(nameof(vf2Create3DNotes));
        var result = function(input.NotesName, input.LaminateName, input.RosetteId, input.RefOriginId,
            input.AnnotationLocked, input.BoundaryType, input.AnnotationLeader, input.SummaryInfo,
            input.ComponentInfo, input.HeaderText, input.ColumnSeparator, input.LinesPerColumn,
            input.ReverseSequence, input.DbPath);
        ThrowIfError(result);
    }

    #endregion

    #endregion

    #region Helper functions
    public void DisplayArrow(int pointId, Vector3d dir)
    {
        var function = Library.GetFunction<vfDisplayArrow>(nameof(vfDisplayArrow));
        double[] vec = [dir.x, dir.y, dir.z];
        var result = function(pointId, vec);
        ThrowIfError(result);
    }

    public GeomEntityType GetEntityType(string entId)
    {
        var function = Library.GetFunction<vfGetEntityType>(nameof(vfGetEntityType));
        var entIdU = int.Parse(entId);
        var result = function(entIdU, out var retval);
        ThrowIfError(result);
        return retval;
    }

    private void ThrowIfError(VFResult result)
    {

        if (result == VFResult.Failure)
        {
            ThrowLastError();
        }
        if (result == VFResult.Cancelled)
        {
            throw new OperationCanceledException();
        }
    }

    private void ThrowLastError()
    {
        var function = Library.GetFunction<vfGetLastError>(nameof(vfGetLastError));
        var nativeResult = function();
        var errWhat = Marshal.PtrToStringAnsi(nativeResult.err_what) ?? "Missing result description";
        switch (nativeResult.source)
        {
            case VfErrorSource.CadErr:
                throw new VfApiException($"CAD exception({nativeResult.err_code}): {errWhat}") { ErrCode = nativeResult.err_code };
            case VfErrorSource.Rpc:
                throw new VfApiException($"RPC exception: {errWhat}");
            case VfErrorSource.Unknown:
                throw new VfApiException($"Unknown exception: {errWhat}");
        }
    }

    private static IntPtr[] StringArrayToPtrArray(string[] strings)
    {
        var retval = new IntPtr[strings.Length + 1];
        for (int i = 0; i < strings.Length; i++)
        {
            retval[i] = Marshal.StringToHGlobalAnsi(strings[i]);
        }
        retval[^1] = IntPtr.Zero;
        return retval;
    }

    private static void FreePtrArray(IntPtr[]? array)
    {
        if (array is null)
            return;
        for (int i = 0; i < array.Length - 1; i++)
        {
            Marshal.FreeHGlobal(array[i]);
        }
    }

    private static (double[], double[], double[]) SplitVectorList(IEnumerable<Vector3d> vectors)
    {
        var xList = new List<double>();
        var yList = new List<double>();
        var zList = new List<double>();
        foreach (var point in vectors)
        {
            xList.Add(point.x);
            yList.Add(point.y);
            zList.Add(point.z);
        }
        xList.Add(double.NaN);
        yList.Add(double.NaN);
        zList.Add(double.NaN);

        return ([.. xList], [.. yList], [.. zList]);
    }

    // Safety: must ensure array has at least numPoints of items
    private static Vector3d[] ReadVectorFromNative(int numPoints, nint arrX, nint arrY, nint arrZ)
    {
        if (numPoints <= 0)
        {
            return [];
        }
        if (arrX == IntPtr.Zero || arrY == IntPtr.Zero || arrZ == IntPtr.Zero)
        {
            throw new ArgumentException("ReadVectorFromNative: input is null");
        }
        Vector3d[] points = new Vector3d[numPoints];
        unsafe
        {
            Span<double> arrXspan = new((void*)arrX, numPoints);
            Span<double> arrYspan = new((void*)arrY, numPoints);
            Span<double> arrZspan = new((void*)arrZ, numPoints);
            for (int i = 0; i < numPoints; i++)
            {
                points[i] = new Vector3d { x = arrXspan[i], y = arrYspan[i], z = arrZspan[i] };
            }
        }

        return points;
    }

    private static List<string> ReadStrArray(IntPtr array)
    {
        var result = new List<string>();
        var i = 0;
        while (true)
        {
            var ptr = Marshal.ReadIntPtr(array, i * IntPtr.Size);
            if (ptr == IntPtr.Zero)
            {
                break;
            }
            result.Add(Marshal.PtrToStringAnsi(ptr) ?? "");
            i++;
        }
        return result;
    }

    private IntPtr MapDataToNative(MBDTreeNode node)
    {
        var fnCreateNode = Library.GetFunction<dbgCreateMBDNode>(nameof(dbgCreateMBDNode));
        var fnAppend = Library.GetFunction<dbgAppendChild>(nameof(dbgAppendChild));
        var root = fnCreateNode(node.ValueType, node.NodeName, node.Key, node.Value);
        foreach (var subnode in node.ChildNodes)
        {
            var subNodePtr = MapDataToNative(subnode);
            fnAppend(root, subNodePtr);
        }
        return root;
    }
    #endregion
}
